package presentation;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import control.CControl;

import entity.ERegister;

public class PMap extends JPanel implements Observer {
	private static final long serialVersionUID = 1L;
	private String imagePath;
	private double tllat, tllong, brlat, brlong;
	private double latSpan, longSpan;
	private Font errorFont = new Font("Arial",Font.BOLD,14);
	private Double[] coordinateSet = new Double[2];
	private ArrayList<Double[]> coordinateSets = new ArrayList<Double[]>();
	private int MAX_ROUTE_LENGTH = 100;
	private int LINE_WIDTH = 3;
	private int DOT_DIAMETER = 10;
	private CControl cControl;
	
	public PMap(String imagePath, double[] coordinates, CControl cControl) {
		this.imagePath = imagePath;
		if (coordinates != null) {
			this.tllat = coordinates[0];
			this.tllong = coordinates[1];
			this.brlat = coordinates[2];
			this.brlong = coordinates[3];
		}
		
		this.latSpan = tllat - brlat;
		this.longSpan = brlong - tllong;
		this.cControl = cControl;
		this.setBackground(Color.white);
	}

	protected void paintComponent(Graphics g) {
    	super.paintComponent(g);
    	Graphics2D g2 = (Graphics2D)g;
    	g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
    			RenderingHints.VALUE_ANTIALIAS_ON);
    	
    	g2.setStroke(new BasicStroke(1));
    	
    	if (!imagePath.equalsIgnoreCase("")) {
    		try {
    			Image mapImage = ImageIO.read(new File(imagePath));
    			double imageWidth = mapImage.getWidth(null);
    			double imageHeight = mapImage.getHeight(null);
    			Double width = this.getWidth()*1.;
    			Double height = this.getHeight()*1.;

    			Double scaledWidth = (height/imageHeight) * imageWidth;
    			Double scaledHeight = (width/imageWidth) * imageHeight-3;

    			if (scaledWidth > width) {
    				scaledWidth = width;
    			} else {
    				scaledHeight = height-3;
    			}

    			int imageX = (width.intValue() - scaledWidth.intValue())/2;
    			int imageY = (height.intValue() - scaledHeight.intValue())/2+3;

    			g2.drawImage(mapImage.getScaledInstance(scaledWidth.intValue(), 
    					scaledHeight.intValue(), Image.SCALE_SMOOTH), imageX, 
    					imageY, null);

    			boolean valid = false;
    			Double[] lastCoords = {};
    			Double[] curCoords = {};
    			if (tllat == 0. && tllong == 0. && brlat == 0. && brlong == 0.) {
    				printError(g2,"No map coodinates given for this map",
    				"Go to settings to enter coordinates");
    			} else {
    				g2.setPaint(new Color(78,90,198));
    				g2.setStroke(new BasicStroke(LINE_WIDTH));

    				if (coordinateSets.size() > 0) {
    					lastCoords = coordinateSets.get(0);
    					valid = true;
    				}

    				for (int i = 1; i < coordinateSets.size(); i++) {
    					curCoords = coordinateSets.get(i);

    					if (curCoords[0] != lastCoords[0] && curCoords[1] != lastCoords[1]) {
    						Double x1 = imageX + scaledWidth.intValue()*lastCoords[0];
    						Double y1 = imageY + scaledHeight.intValue() - scaledHeight.intValue()*lastCoords[1];
    						Double x2 = imageX + scaledWidth.intValue()*curCoords[0];
    						Double y2 = imageY + scaledHeight.intValue() - scaledHeight.intValue()*curCoords[1];
    						g2.drawLine(x1.intValue(), y1.intValue(), x2.intValue(), y2.intValue());	
    					}

    					lastCoords = curCoords;
    				}

    				if (coordinateSets.size() > 0) {
    					curCoords = coordinateSets.get(coordinateSets.size()-1);
    					Double x = imageX + scaledWidth.intValue()*curCoords[0];
    					Double y = imageY + scaledHeight.intValue() - scaledHeight.intValue()*curCoords[1];

    					g2.fillOval(x.intValue()-DOT_DIAMETER/2, 
    							y.intValue()-DOT_DIAMETER/2, DOT_DIAMETER, 
    							DOT_DIAMETER);

    					g2.setStroke(new BasicStroke(1));
    					g2.setPaint(Color.black);
    					g2.drawOval(x.intValue()-DOT_DIAMETER/2, 
    							y.intValue()-DOT_DIAMETER/2, DOT_DIAMETER, 
    							DOT_DIAMETER);
    				}

    				if (!valid) {
    					printError(g2,"No coordinates inside map","");
    				}
    			}
    		} catch (IOException e) {
    			printError(g2,"Unable to read map image","");
    			cControl.addError("Unable to read map image: " + e.getMessage());
    		}
    	} else {
    		printError(g2,"No images available","Put map image in /maps and restart");
    	}
	}
	
	private boolean checkCoordinate(Double[] coords) {
		boolean within = true;
		
		if (!(coords[0] < tllat && coords[0] > brlat)) {
			within = false;
		} else if (!(coords[1] > tllong && coords[1] < brlong)) {
			within = false;
		}
		
		return within;
	}
	
	private void printError(Graphics2D g2, String errorLine1, String errorLine2) {
		g2.setPaint(Color.red);
		g2.setFont(errorFont);
		
		FontRenderContext frc = g2.getFontRenderContext();
		
		Double error1Width = errorFont.getStringBounds(errorLine1, frc).getWidth();
		Double error2Width = errorFont.getStringBounds(errorLine2, frc).getWidth();
		int line1x = this.getWidth()/2 - error1Width.intValue()/2;
		int line2x = this.getWidth()/2 - error2Width.intValue()/2;
		
		g2.drawString(errorLine1, line1x, this.getHeight()/2-10);
		g2.drawString(errorLine2, line2x, this.getHeight()/2+10);
	}
	
	public void setMap(String mapPath, double[] coordinates) {
		this.imagePath = mapPath;
		this.tllat = coordinates[0];
		this.tllong = coordinates[1];
		this.brlat = coordinates[2];
		this.brlong = coordinates[3];
		this.latSpan = tllat - brlat;
		this.longSpan = brlong - tllong;
		this.updateUI();
	}
	
	public synchronized void clearRoute() {
		coordinateSets.clear();
	}

	@Override
	public synchronized void update(Observable obs, Object arg) {
		if (obs instanceof ERegister && arg instanceof Number) {
			if (((ERegister) obs).getName().equalsIgnoreCase("GPSLat")) {
				coordinateSet[0] = (Double) arg;
			} else if (((ERegister) obs).getName().equalsIgnoreCase("GPSLong")) {
				coordinateSet[1] = (Double) arg;
				
				if (checkCoordinate(coordinateSet)) {
					Double[] relCoordinateSet = new Double[2];
					relCoordinateSet[0] = ((coordinateSet[1] - tllong)/longSpan);
					relCoordinateSet[1] = ((coordinateSet[0] - brlat)/latSpan);
					
					boolean isNewCoordinates = false;
					if (coordinateSets.size() > 0) {
						Double[] newestCoords = coordinateSets.get(coordinateSets.size()-1);
						if (newestCoords[0] != relCoordinateSet[0] || 
								newestCoords[1] != relCoordinateSet[1]) {
							isNewCoordinates = true;
						}
					} else {
						isNewCoordinates = true;
					}
					
					if (isNewCoordinates) {
						coordinateSets.add(relCoordinateSet);
						
						if (coordinateSets.size() > MAX_ROUTE_LENGTH) {
							coordinateSets.remove(0);
						}
					}
					
					this.repaint();
				}
			}
		}
	}
}
